/*
 ------------------------------------------------------------------------------
 Copyright (C) 2006-2007 Team Blur.

 This file is part of the Quantum Engine source code.

 The Quantum Engine source code is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or (at your
 option) any later version.

 The Quantum Engine source code is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 more details.

 You should have received a copy of the GNU General Public License along with
 the Quantum Engine source code; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 ------------------------------------------------------------------------------
*/


#include "r_local.h"


/*
 ==================
 R_AddFoliageSurface

 Special case for foliage surfaces because they are just foliage instance
 containers
 ==================
*/
static void R_AddFoliageSurface (surface_t *surface, material_t *material, entity_t *entity, const odVec3 &viewOrigin, int cullBits){

	foliageInstance_t	*foliageInstance;
	float				fovScale;
	int					i;

	if (r_skipFoliage->integerValue)
		return;

	fovScale = M_Tan(tr.renderView.fovX * (M_PI_HALF / 180.0f));

	// Cull by distance
	if (!surface->bounds.IntersectsSphere(viewOrigin, surface->foliageMaxDistance * (1.0f / fovScale)))
		return;

	// Cull bounds
	if (cullBits){
		if (entity == tr.scene->worldEntity)
			cullBits = R_CullBounds(surface->bounds, cullBits);
		else
			cullBits = R_CullEntityBounds(surface->bounds, cullBits, entity);

		if (cullBits == CULL_OUT)
			return;
	}

	// Mark as visible for this view
	surface->viewCount = tr.viewCount;

	tr.pc.leafSurfaces++;

	// If it has a single foliage instance, no need to cull again
	if (surface->numFoliageInstances == 1)
		cullBits = 0;

	// Add all the foliage instances
	for (i = 0, foliageInstance = surface->foliageInstances; i < surface->numFoliageInstances; i++, foliageInstance++){
		// Cull by distance
		if (foliageInstance->origin.DistanceFast(viewOrigin) * fovScale >= surface->foliageMaxDistance)
			continue;

		// Cull sphere
		if (cullBits){
			if (entity == tr.scene->worldEntity){
				if (R_CullSphere(foliageInstance->origin, surface->foliageRadius, cullBits) == CULL_OUT)
					continue;
			}
			else {
				if (R_CullEntitySphere(foliageInstance->origin, surface->foliageRadius, cullBits, entity) == CULL_OUT)
					continue;
			}
		}

		// Mark as visible for this view
		foliageInstance->viewCount = tr.viewCount;

		tr.pc.leafFoliages++;

		// Add the draw mesh
		R_AddDrawMesh(MESH_FOLIAGE, foliageInstance, material, entity);
	}
}

/*
 ==================
 R_AddSurface
 ==================
*/
static void R_AddSurface (surface_t *surface, material_t *material, entity_t *entity, const odVec3 &viewOrigin, int cullBits){

	int		side;

	if (surface->type == ST_FOLIAGE){
		R_AddFoliageSurface(surface, material, entity, viewOrigin, cullBits);
		return;
	}

	// Cull face
	if (surface->plane && material->cullType != CT_TWO_SIDED){
		side = surface->plane->PointOnSide(viewOrigin);

		if (material->cullType == CT_FRONT_SIDED){
			if (side != PLANESIDE_FRONT)
				return;
		}
		else {
			if (side != PLANESIDE_BACK)
				return;
		}
	}

	// Cull bounds
	if (cullBits){
		if (entity == tr.scene->worldEntity)
			cullBits = R_CullBounds(surface->bounds, cullBits);
		else
			cullBits = R_CullEntityBounds(surface->bounds, cullBits, entity);

		if (cullBits == CULL_OUT)
			return;
	}

	// Mark as visible for this view
	surface->viewCount = tr.viewCount;

	tr.pc.leafSurfaces++;

	// Set up subview if needed
	if (material->subviewType != ST_NONE){
		if (surface->plane)
			R_SetupSubview(material, entity, surface->plane);
		else
			R_SetupSubview(material, entity, surface->facePlanes);
	}

	// Add the draw mesh
	R_AddDrawMesh(MESH_SURFACE, surface, material, entity);
}


/*
 ==============================================================================

 WORLD MODEL

 ==============================================================================
*/


/*
 ==================
 R_MarkLeaves
 ==================
*/
static void R_MarkLeaves (void){

	node_t		*node, *leaf;
	const byte	*pvs;
	byte		areaBits[BSP_MAX_AREAS/8];
	uint		areaBitsModified = 0;
	int			i;

	if (r_lockVisibility->integerValue)
		return;

	// Get the current view cluster
	if (tr.viewParms.viewType == VIEW_MAIN || tr.viewParms.viewType == VIEW_PORTALSKY)
		leaf = R_PointInLeaf(tr.renderView.origin);
	else
		leaf = R_PointInLeaf(tr.subviewParms.pvsOrigin);

	// Get the PVS and area bits
	pvs = CM_ClusterPVS(leaf->cluster);

	CM_WriteAreaBits(leaf->area, areaBits);

	// Check if the area bits have been modified and copy them
	for (i = 0; i < sizeof(tr.areaBits) / 4; i++){
		areaBitsModified |= ((uint *)tr.areaBits)[i] ^ ((uint *)areaBits)[i];

		((uint *)tr.areaBits)[i] = ((uint *)areaBits)[i];
	}

	// If the view cluster and area bits haven't changed, we don't need to mark
	// everything again
	if (!r_skipVisibility->integerValue && tr.viewCluster == leaf->cluster && !areaBitsModified)
		return;

	if (r_showCluster->integerValue){
		if (tr.viewCluster != leaf->cluster)
			Com_Printf("cluster:%i area:%i\n", leaf->cluster, leaf->area);
	}

	tr.visCount++;
	tr.viewCluster = leaf->cluster;

	if (r_skipVisibility->integerValue || leaf->cluster == -1 || leaf->area == -1){
		// Mark everything
		for (i = 0, node = tr.world->nodes; i < tr.world->numNodes; i++, node++)
			node->visCount = tr.visCount;

		return;
	}

	// Mark the leaves and nodes that are visible from the current cluster
	for (i = 0, leaf = tr.world->nodes; i < tr.world->numNodes; i++, leaf++){
		if (leaf->cluster == -1 || leaf->area == -1)
			continue;

		// Check the PVS
		if (!(pvs[leaf->cluster >> 3] & BIT(leaf->cluster & 7)))
			continue;

		// Check area connection
		if (!(tr.areaBits[leaf->area >> 3] & BIT(leaf->area & 7)))
			continue;

		node = leaf;
		do {
			if (node->visCount == tr.visCount)
				break;
			node->visCount = tr.visCount;

			node = node->parent;
		} while (node);
	}
}

/*
 ==================
 R_RecursiveWorldNode
 ==================
*/
static void R_RecursiveWorldNode (node_t *node, int cullBits){

	surface_t	*surface;
	material_t	*material;
	int			i;

	if (node->visCount != tr.visCount)
		return;		// Not visible

	// Cull bounds
	if (cullBits){
		cullBits = R_CullBounds(node->bounds, cullBits);

		if (cullBits == CULL_OUT)
			return;
	}

	// Mark as visible for this view
	node->viewCount = tr.viewCount;

	// If a node, recurse down the children
	if (!node->isLeaf){
		R_RecursiveWorldNode(node->children[0], cullBits);
		R_RecursiveWorldNode(node->children[1], cullBits);
		return;
	}

	// Leaf node
	if (!node->numLeafSurfaces)
		return;

	tr.pc.leafs++;

	// If it has a single surface, no need to cull again
	if (node->numLeafSurfaces == 1)
		cullBits = 0;

	// Add all the surfaces
	for (i = 0; i < node->numLeafSurfaces; i++){
		surface = node->leafSurfaces[i];

		if (surface->worldCount == tr.worldCount)
			continue;		// Already added this surface from another leaf
		surface->worldCount = tr.worldCount;

		// Get the material
		material = surface->material;

		if (!material->numStages)
			continue;		// Don't bother drawing

		// Add the surface
		R_AddSurface(surface, material, tr.scene->worldEntity, tr.renderView.origin, cullBits);
	}
}

/*
 ==================
 R_AddWorld
 ==================
*/
void R_AddWorld (void){

	if (!tr.viewParms.primaryView)
		return;

	// Bump world count
	tr.worldCount++;

	// Mark leaves
	R_MarkLeaves();

	// Walk the BSP tree
	if (!r_skipCulling->integerValue)
		R_RecursiveWorldNode(tr.world->nodes, 31);
	else
		R_RecursiveWorldNode(tr.world->nodes, 0);
}

/*
 ==================
 R_PointInLeaf
 ==================
*/
node_t *R_PointInLeaf (const odVec3 &point){

	node_t	*node;
	int		side;

	if (!tr.world)
		Com_Error(false, "R_PointInLeaf: NULL world");

	node = tr.world->nodes;

	while (1){
		if (node->isLeaf)
			break;

		side = node->plane->PointOnSide(point);

		if (side == PLANESIDE_FRONT)
			node = node->children[0];
		else
			node = node->children[1];
	}

	return node;
}


/*
 ==============================================================================

 INLINE MODELS

 ==============================================================================
*/


/*
 ==================
 R_AddInlineModel
 ==================
*/
void R_AddInlineModel (entity_t *entity){

	inlineModel_t	*model = (inlineModel_t *)entity->e.model->data;
	surface_t		*surface;
	material_t		*material;
	odVec3			viewOrigin;
	int				cullBits;
	int				i;

	// Cull bounds
	if (!r_skipCulling->integerValue && !entity->e.depthHack){
		cullBits = R_CullEntityBounds(model->bounds, 31, entity);

		if (cullBits == CULL_OUT)
			return;
	}
	else
		cullBits = 0;

	// Mark as visible for this view
	entity->viewCount = tr.viewCount;

	tr.pc.entities++;

	// Perform a callback if needed
	if (entity->e.callback){
		entity->e.callback(&entity->e);

		entity->e.callback = NULL;
	}

	// Transform view origin into local space
	entity->e.axis.ProjectVector(tr.renderView.origin - entity->e.origin, viewOrigin);

	// If it has a single surface, no need to cull again
	if (model->numSurfaces == 1)
		cullBits = 0;

	// Add all the surfaces
	for (i = 0, surface = model->surfaces; i < model->numSurfaces; i++, surface++){
		// Get the material
		if (entity->e.material)
			material = entity->e.material;
		else
			material = surface->material;

		if (!material->numStages)
			continue;		// Don't bother drawing

		// Add the surface
		R_AddSurface(surface, material, entity, viewOrigin, cullBits);
	}
}
